/*
 moxl - Modular XML Library
 Copyright (c) 2007, Malcolm Sparks

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package moxl.dom.raw;

import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;

import moxl.dom.MoxlElement;
import moxl.dom.MoxlNode;
import moxl.dom.QualifiedName;
import moxl.dom.XmlNamespaceContext;
import moxl.dom.support.MoxlNodeList;
import moxl.raw.RawCharacterData;
import moxl.raw.RawComment;
import moxl.raw.RawElement;
import moxl.raw.RawNode;

class RawBackedElementChildren
{
	private final RawElement raw;
	private MoxlNodeList copy;
	private final XmlNamespaceContext namespaceContext;
	private final MoxlElement parent;

	public RawBackedElementChildren(RawElement raw, MoxlElement parent, XmlNamespaceContext namespaceContext)
	{
		if (parent == null)
		{
			throw new IllegalArgumentException("parent cannot be null");
		}
		this.raw = raw;
		this.parent = parent;
		this.namespaceContext = namespaceContext;
	}

	public void appendChild(MoxlNode node)
	{
		ensureCopy();
		copy.append(node);
	}

	private void ensureCopy()
	{
		if (copy == null)
		{
			copy = new MoxlNodeList(parent);
			for (RawNode node : raw.getChildNodes())
			{
				if (node instanceof RawElement)
				{
					copy.append(new RawBackedElement((RawElement) node, namespaceContext));
				}
				else if (node instanceof RawCharacterData)
				{
					copy.append(new RawBackedCharacterData((RawCharacterData) node));
				}
				else if (node instanceof RawComment)
				{
					copy.append(new RawBackedComment((RawComment) node));
				}
				else
				{
					throw new UnsupportedOperationException("TODO: type is " + node.getClass().getName());
				}
			}
		}
	}

	public List<MoxlElement> getChildElements()
	{
		ensureCopy();
		List<MoxlElement> result = new ArrayList<MoxlElement>();
		for (MoxlNode node : copy)
		{
			if (node instanceof MoxlElement)
			{
				MoxlElement element = (MoxlElement) node;
				result.add(element);
			}
		}
		return result;
	}

	public MoxlElement getChildElement(int index)
	{
		ensureCopy();
		int count = index; 
		for (MoxlNode node : copy)
		{
			if (node instanceof MoxlElement)
			{
				MoxlElement element = (MoxlElement) node;
				if (count == 0)
				{
					return element;
				}
				else
				{
					count--;
				}
			}
		}
		String cardinal = cardinal(count + 1);
		throw new NoSuchElementException("No " + cardinal + " element in " + this + (cardinal.equals("nth") ? " where n is " + (count + 1) : ""));
	}

	private String cardinal(int number)
	{
		switch (number)
		{
		case 1:
			return "first";
		case 2:
			return "second";
		case 3:
			return "third";
		case 4:
			return "fourth";
		case 5:
			return "fifth";
		default:
			return "nth";
		}
	}

	public MoxlNode removeChildNode(int index)
	{
		ensureCopy();
		return copy.remove(index);
	}

	public boolean isEmpty()
	{
		if (copy == null && raw.getChildNodes().isEmpty())
		{
			return true;
		}
		return copy.isEmpty();
	}

	public List<MoxlNode> getChildNodes()
	{
		ensureCopy();
		return copy.getNodes();
	}

	public MoxlNode getChildNode(int index)
	{
		ensureCopy();
		return copy.getNodeAtIndex(index);
	}

	public int countChildNodes()
	{
		if (copy == null)
		{
			return raw.countNodes();
		}
		else
		{
			return copy.countNodes();
		}
	}

	public void insertChild(MoxlNode node)
	{
		ensureCopy();
		copy.insert(node);
	}

	public boolean containsCharacterData()
	{
		if (copy == null)
		{
			return raw.containsCharacterData();
		}
		else
		{
			return copy.containsCharacterData();
		}
	}

	public boolean containsCharacterDataExcludingWhitespace()
	{
		if (copy == null)
		{
			return raw.containsCharacterDataExcludingWhitespace();
		}
		else
		{
			return copy.containsCharacterDataExcludingWhitespace();
		}
	}

	public MoxlElement removeChildElement(int index)
	{
		ensureCopy();
		int count = index;
		int nodeIndex = 0;
		for (MoxlNode node : copy)
		{
			if (node instanceof MoxlElement)
			{
				count--;
			}
			if (count < 0)
			{
				break;
			}
			else
			{
				nodeIndex++;
			}
		}
		if (nodeIndex >= countChildNodes())
		{
			throw new NoSuchElementException();
		}
		return (MoxlElement) copy.remove(nodeIndex);
	}

	public int countChildElements()
	{
		if (copy == null)
		{
			return raw.countElements();
		}
		else
		{
			return copy.countElements();
		}
	}

	public void replace(MoxlNode old, MoxlNode replacement)
	{
		ensureCopy();
		copy.replace(old, replacement);
	}

	public boolean containsChild(MoxlNode child)
	{
		if (copy == null)
		{
			return false;
		}
		for (MoxlNode node : copy)
		{
			if (child.equals(node))
			{
				return true;
			}
		}
		return false;
	}

	public List<MoxlNode> removeAll()
	{
		ensureCopy();
		return copy.detachAll();
	}

	public void replace(MoxlNode old, List<? extends MoxlNode> replacements)
	{
		ensureCopy();
		int index = copy.indexOf(old);
		copy.remove(index);
		for (MoxlNode replacement : replacements)
		{
			copy.insert(index++, replacement);
		}
	}

	public String getContentAsString()
	{
		if (copy != null)
		{
			return copy.getContentAsString();
		}
		if (raw.countNodes() == 1)
		{
			RawNode firstChild = raw.getChildNode(0);
			if (firstChild instanceof RawCharacterData)
			{
				return ((RawCharacterData) firstChild).getText();
			}
			else
			{
				throw new IllegalStateException("Child node is not character data");
			}
		}
		else
		{
			StringBuilder result = new StringBuilder();
			for (RawNode node : raw.getChildNodes())
			{
				if (!(node instanceof RawCharacterData))
				{
					throw new IllegalStateException("One child node is not character data");
				}
				result.append(((RawCharacterData) node).getText());
			}
			return result.toString();
		}
	}

	public List<MoxlElement> getChildElements(QualifiedName name)
	{
		if (name == null)
		{
			throw new IllegalArgumentException("name cannot be null");
		}
		ensureCopy();
		List<MoxlElement> result = new ArrayList<MoxlElement>();
		for (MoxlNode node : copy)
		{
			if (node instanceof MoxlElement)
			{
				MoxlElement element = (MoxlElement) node;
				if (element.getName().equals(name))
				{
					result.add(element);
				}
			}
		}
		return result;
	}

	public MoxlElement getChildElement(QualifiedName name, int index)
	{
		ensureCopy();
		int count = index;
		for (MoxlNode node : copy)
		{
			if (node instanceof MoxlElement)
			{
				MoxlElement element = (MoxlElement) node;
				if (!element.getName().equals(name))
				{
					continue;
				}
				if (count == 0)
				{
					return element;
				}
				else
				{
					count--;
				}
			}
		}
		String cardinal = cardinal(count + 1);
		throw new NoSuchElementException("No " + cardinal + " " + name + " element in " + parent + (cardinal.equals("nth") ? " where n is " + (count + 1) : ""));
	}

}
